﻿using System.Collections.Generic;
using UnityEngine;
using org.critterai.nav.u3d;
using org.critterai.nav;

public class CAINavmeshController
{
    public int maxPathSize = 256;
    public int maxCornerCount = 256;

    private NavManagerProvider mProvider;
    private NavGroup mNavGroup;
    private NavmeshPoint mPathStart;
    private NavmeshPoint mPathEnd;
    private NavmeshQuery mNavmeshQuery;
    private uint[] mResultPathArray = new uint[256];
    private int mPathCount;
    private uint[] mRaycastPathBuffer = new uint[256];
    private uint[] mVisitedBuffer = new uint[256];
    private List<Vector3> mPathPointList = new List<Vector3>();
    private Vector3[] mResultPointArray = new Vector3[256];
    private WaypointFlag[] mWaypointFlagArray = new WaypointFlag[256];
    private uint[] mResultRefArray = new uint[256];

    public void DoStart()
    {

    }

    public void DoUpdate()
    {

    }

    public void DoDestroy()
    {

    }

    public void SetNavManagerProvider(NavManagerProvider provider)
    {
        mProvider = provider;
    }

    public void StartNavMesh()
    {
        if (mProvider != null)
        {
            NavManager navManager = mProvider.CreateManager();
            if (navManager != null)
            {
                mNavGroup = navManager.NavGroup;
                mPathStart = new NavmeshPoint();
                mPathEnd = new NavmeshPoint();
                mNavmeshQuery = mNavGroup.query;
            }
        }
    }

    private NavmeshPoint GetNavmeshPoint(Vector3 searchPoint, bool useWideExtent = false)
    {
        NavmeshPoint navmeshPoint = new NavmeshPoint();
        if (mNavGroup.query != null && mNavGroup.filter != null)
        {
            mNavGroup.query.GetNearestPoint(searchPoint, mNavGroup.extents, mNavGroup.filter, out navmeshPoint);

            if (useWideExtent && navmeshPoint.polyRef == 0)
            {
                mNavGroup.query.GetNearestPoint(searchPoint, mProvider.WideExtents, mNavGroup.filter, out navmeshPoint);
            }
        }

        return navmeshPoint;
    }

    public List<Vector3> FindPath(Vector3 startPoint, Vector3 endPoint)
    {
        mPathStart = GetNavmeshPoint(startPoint, true);
        mPathEnd = GetNavmeshPoint(endPoint, true);
        mPathPointList.Clear();

        if (mPathStart.polyRef != 0 && mPathEnd.polyRef != 0)
        {
            NavStatus status = mNavGroup.query.FindPath(mPathStart, mPathEnd, mNavGroup.filter, mResultPathArray, out mPathCount);

            if (NavUtil.Succeeded(status) && mPathCount > 0)
            {
                Vector3 actualEndPoint = Vector3.zero;
                if (mPathEnd.polyRef != mResultPathArray[mResultPathArray.Length - 1])
                {
                    mNavmeshQuery.GetNearestPoint(mResultPathArray[mResultPathArray.Length - 1], endPoint, out actualEndPoint);
                }

                int resultCount = 0;
                status = mNavmeshQuery.GetStraightPath(startPoint, endPoint, mResultPathArray, 0, mPathCount, mResultPointArray, mWaypointFlagArray, mResultRefArray, out resultCount);
                if (NavUtil.Succeeded(NavStatus.Sucess) && resultCount > 0)
                {
                    mPathPointList.Clear();
                    for (int i = 0; i < resultCount; ++i)
                    {
                        mPathPointList.Add(mResultPointArray[i]);
                    }
                }
            }
        }

        return mPathPointList;
    }

    public bool GetPolyHeight(Vector3 point, out float height)
    {
        height = 0;

        NavmeshPoint navmeshPoint = GetNavmeshPoint(point);
        if (navmeshPoint.polyRef != 0)
        {
            NavStatus navStatus = mNavGroup.query.GetPolyHeight(navmeshPoint, out height);
            if (navStatus == NavStatus.Sucess)
            {
                return true;
            }
        }

        return false;
    }

    public bool IsPositionWalkable(Vector3 point)
    {
        NavmeshPoint navmeshPoint = GetNavmeshPoint(point);
        if (navmeshPoint.polyRef != 0)
        {
            return true;
        }

        return false;
    }

    public bool Raycast(Vector3 startPoint, Vector3 endPoint)
    {
        float hitParam = 0.0f;
        Vector3 hitNormal = Vector3.zero;
        int pathCount = 0;
        NavmeshPoint startNavPoint = GetNavmeshPoint(startPoint);
        NavmeshPoint endNavPoint = GetNavmeshPoint(endPoint);
        NavStatus navStatus = mNavGroup.query.Raycast(startNavPoint, endNavPoint.point, mNavGroup.filter, out hitParam, out hitNormal, mRaycastPathBuffer, out pathCount);

        if (NavUtil.Succeeded(navStatus) && hitParam > 1)
        {
            return true;
        }

        return false;
    }

    public bool MoveAlongSurface(Vector3 startPoint, Vector3 endPoint, out Vector3 resultPoint)
    {
        NavmeshPoint pathStart = GetNavmeshPoint(startPoint);
        NavmeshPoint pathEnd = GetNavmeshPoint(endPoint);
        resultPoint = Vector3.zero;

        if (pathStart.polyRef != 0 && pathEnd.polyRef != 0)
        {
            int mVisitedCount;
            NavStatus navStatus = mNavGroup.query.MoveAlongSurface(pathStart, endPoint, mNavGroup.filter, out resultPoint, mVisitedBuffer, out mVisitedCount);
            if (navStatus == NavStatus.Sucess)
            {
                return true;
            }
        }

        return false;
    }
}
